using System;
using System.Collections.Generic;
using System.Text;
using System.Diagnostics;
using System.ComponentModel;

namespace Sio.Mdm.Utils.Collections
{
    /// <summary>
    /// Represents sorted dictionary. Store and retreive items by key.
    /// </summary>
    /// <typeparam name="TKey"></typeparam>
    /// <typeparam name="TValue"></typeparam>    
    [Serializable]
    [DebuggerDisplay("Count = {Count}")]
    [CLSCompliant(true)]
    public class BaseDictionary<TKey, TValue> : Dictionary<TKey, TValue>, IDisposable
    {
        #region Fields
        private bool IsDisposable = typeof(IDisposable).IsAssignableFrom(typeof(TValue));
        #endregion

        #region Construction
        /// <summary>
        /// Initialize dictionray
        /// </summary>
        public BaseDictionary()
        {
        }
        /// <summary>
        /// Initialize dictionary and copy passed elemnts
        /// </summary>
        /// <param name="dictionary"></param>
        public BaseDictionary(IDictionary<TKey, TValue> dictionary)
            : base(dictionary)
        {
        }
        /// <summary>
        /// Initialize dictionary and use passed comparer for sorting items
        /// </summary>
        /// <param name="comparer"></param>
        public BaseDictionary(IEqualityComparer<TKey> comparer)
            : base(comparer)
        {
        }
        /// <summary>
        /// Initialize dictionary with passed capacity
        /// </summary>
        /// <param name="capacity"></param>
        public BaseDictionary(int capacity)
            : base(capacity)
        {
        }
        /// <summary>
        /// Initialize dictionary with passed items and comparer
        /// </summary>
        /// <param name="dictionary"></param>
        /// <param name="comparer"></param>
        public BaseDictionary(IDictionary<TKey, TValue> dictionary, IEqualityComparer<TKey> comparer)
            : base(dictionary, comparer)
        {
        }
        /// <summary>
        /// Initialize dictionary to passed capacity and use passed comparer for inserting finding items in dictionry
        /// </summary>
        /// <param name="capacity"></param>
        /// <param name="comparer"></param>
        public BaseDictionary(int capacity, IEqualityComparer<TKey> comparer)
            : base(comparer)
        {
        }
        /// <summary>
        /// Finalizer
        /// </summary>
        ~BaseDictionary()
        {
            Dispose(false);
        }
        #endregion

        #region Properties

        #region Public

        #region IsDisposed
        private bool _isDisposed;
        /// <summary>
        /// Get wheater dictionary was disposed
        /// </summary>
        public bool IsDisposed
        {
            get
            {
                return _isDisposed;
            }
        }
        #endregion

        #endregion

        #endregion

        #region Methods

        #region Public

        #region Indexer
        /// <summary>
        /// Get or set item with specified key
        /// </summary>
        /// <param name="key"></param>
        /// <returns></returns>
        public new TValue this[TKey key]
        {
            get
            {
                if (_isDisposed)
                {
                    throw new ObjectDisposedException(GetType().ToString());
                }

                return base[key];
            }
            set
            {
                if (_isDisposed)
                {
                    throw new ObjectDisposedException(GetType().ToString());
                }

                if (!OnBeforeSet(key, value))
                {
                    return;
                }

                base[key] = value;
                OnAfterSet(key, value);
            }
        }        
        #endregion

        #region Add
        /// <summary>
        /// Add new item to dictionary
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        public new void Add(TKey key, TValue value)
        {
            if (_isDisposed)
            {
                throw new ObjectDisposedException(GetType().ToString());
            }

            if (!OnBeforeAdd(key, value))
            {
                return;
            }

            base.Add(key, value);
            OnAfterAdd(key, value);
        } 
        #endregion

        #region Clear
        /// <summary>
        /// Clear existing items from dictionary
        /// </summary>
        public new void Clear()
        {
            if (_isDisposed)
            {
                throw new ObjectDisposedException(GetType().ToString());
            }

            if (!OnBeforeClear())
            {
                return;
            }

            base.Clear();
            OnAfterClear();
        } 
        #endregion

        #region Remove
        /// <summary>
        /// Remove item with specified key from dictionary
        /// </summary>
        /// <param name="key"></param>
        public new void Remove(TKey key)
        {
            if (_isDisposed)
            {
                throw new ObjectDisposedException(GetType().ToString());
            }

            if (!ContainsKey(key))
            {
                return;
            }

            TValue item = base[key];

            if (!OnBeforeRemove(key, item))
            {
                return;
            }

            base.Remove(key);
            OnAfterRemove(key, item);
        }   
        #endregion

        #region ReplaceKey
        /// <summary>
        /// Replace item's key with new one. Function does notginh if key is not found in
        /// dictionary.
        /// </summary>
        /// <param name="oldKey">Previous key of item</param>
        /// <param name="newKey">New key to use in dictionary</param>
        public void ReplaceKey(TKey oldKey, TKey newKey)
        {
            if (!ContainsKey(oldKey))
            {
                return;
            }

            TValue value = base[oldKey];
            
            base.Remove(oldKey);
            base.Add(newKey, value);

            OnKeyReplaced(oldKey, newKey);
        }
        #endregion

        #region GetValues
        /// <summary>
        /// Get copy of values 
        /// </summary>
        /// <returns>Instance of with values</returns>
        public BaseList<TValue> GetValues()
        {
            return new BaseList<TValue>(base.Values);
        }
        #endregion

        #region IDisposable Members
        /// <summary>
        /// Dispose dictionary and items if they implement <see cref="IDisposable"/>
        /// </summary>
        public void Dispose()
        {
            if (_isDisposed)
            {
                return;
            }

            if (IsDisposable)
            {
                foreach (TValue item in Values)
                {
                    (item as IDisposable).Dispose();
                }

                Clear();
            }

            Dispose(true);
            _isDisposed = true;
        }
        #endregion

        #endregion

        #region Protected

        #region OnBeforeAdd, OnAfterAdd
        /// <summary>
        /// Called before item id added to dictionary
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        /// <returns>True to add item; false to prevent adding</returns>
        protected virtual bool OnBeforeAdd(TKey key, TValue value)
        {
            if (BeforeAdd != null)
            {
                KeyValuePairCancelEventArgs<TKey, TValue> args =
                    new KeyValuePairCancelEventArgs<TKey, TValue>(key, value);
                
                BeforeAdd(this, args);

                return !args.Cancel;
            }

            return true;
        }
        /// <summary>
        /// Called after item is added to dictionary
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        protected virtual void OnAfterAdd(TKey key, TValue value)
        {
            if (AfterAdd != null)
            {
                KeyValuePairEventArgs<TKey, TValue> e = new KeyValuePairEventArgs<TKey, TValue>(key, value);
                AfterAdd(this, e);
            }
        }
        #endregion

        #region OnBeforeRemove, OnAfterRemove
        /// <summary>
        /// Called before item is removed from dictionary
        /// </summary>
        /// <param name="key">Key to be removed</param>
        /// <param name="value">Value to be removed</param>
        /// <returns>True if item is allowed to be removed</returns>
        protected virtual bool OnBeforeRemove(TKey key, TValue value)
        {
            if (BeforeRemove != null)
            {

                KeyValuePairCancelEventArgs<TKey, TValue> args =
                    new KeyValuePairCancelEventArgs<TKey, TValue>(key, value);

                BeforeRemove(this, args);

                return !args.Cancel;
            }

            return true;
        }
        /// <summary>
        /// Called after ite was removed from dictionary
        /// </summary>
        /// <param name="key">Key that was removed</param>
        /// <param name="value">Value that was removed</param>
        protected virtual void OnAfterRemove(TKey key, TValue value)
        {
            if (AfterRemove != null)
            {
                KeyValuePairEventArgs<TKey, TValue> e = new KeyValuePairEventArgs<TKey, TValue>(key, value);
                AfterRemove(this, e);
            }
        }
        #endregion

        #region OnBeforeSet, OnAfterSet
        /// <summary>
        /// Called before item value of specified key is set
        /// </summary>
        /// <param name="key"></param>
        /// <param name="newValue"></param>
        /// <returns>True if item should be added</returns>
        protected virtual bool OnBeforeSet(TKey key, TValue newValue)
        {
            if (BeforeSet != null)
            {
                KeyValuePairCancelEventArgs<TKey, TValue> args =
                    new KeyValuePairCancelEventArgs<TKey, TValue>(key, newValue);

                BeforeSet(this, args);

                return !args.Cancel;
            }

            return true;
        }
        /// <summary>
        /// Called after value of specified key was set
        /// </summary>
        /// <param name="key"></param>
        /// <param name="value"></param>
        protected virtual void OnAfterSet(TKey key, TValue value)
        {
            if (AfterSet != null)
            {
                KeyValuePairEventArgs<TKey, TValue> e = new KeyValuePairEventArgs<TKey, TValue>(key, value);
                AfterSet(this, e);
            }
        }
        #endregion

        #region OnBeforeClear, OnAfterClear
        /// <summary>
        /// Called before dictionary is cleaerd of all items
        /// </summary>
        /// <returns>True for clear to proceede</returns>
        protected virtual bool OnBeforeClear()
        {
            if (BeforeClear != null)
            {
                CancelEventArgs args = new CancelEventArgs();

                BeforeClear(this, args);

                return !args.Cancel;
            }

            return true;
        }
        /// <summary>
        /// Called after dictionary was cleared
        /// </summary>
        protected virtual void OnAfterClear()
        {
            if (AfterClear != null)
            {
                AfterClear(this, EventArgs.Empty);
            }
        }
        #endregion

        #region OnKeyReplaced
        /// <summary>
        /// Invokes <see cref="KeyReplaced"/> event
        /// </summary>
        /// <param name="oldKey">Previous key of item</param>
        /// <param name="newKey">New key of item</param>
        protected virtual void OnKeyReplaced(TKey oldKey, TKey newKey)
        {
            if (KeyReplaced != null)
            {
                KeyReplacedEventArgs<TKey> e = new KeyReplacedEventArgs<TKey>(oldKey, newKey);
                KeyReplaced(this, e);
            }
        } 
        #endregion

        #region Dispose
        /// <summary>
        /// Called when data allocated should be disposed
        /// </summary>
        /// <param name="disposing">True if managed resources should be disposed</param>
        protected virtual void Dispose(bool disposing)
        {
        } 
        #endregion

        #endregion

        #endregion

        #region Events

        #region Public
        /// <summary>
        /// Fired before item is added to dictionary
        /// </summary>
        public event EventHandler<KeyValuePairCancelEventArgs<TKey, TValue>> BeforeAdd;
        /// <summary>
        /// Fired after item was added to distionary
        /// </summary>
        public event EventHandler<KeyValuePairEventArgs<TKey, TValue>> AfterAdd;
        /// <summary>
        /// Fired before item is removed from dictionary
        /// </summary>
        public event EventHandler<KeyValuePairCancelEventArgs<TKey, TValue>> BeforeRemove;
        /// <summary>
        /// Fired after item is removed from dictionary
        /// </summary>
        public event EventHandler<KeyValuePairEventArgs<TKey, TValue>> AfterRemove;
        /// <summary>
        /// Fired before value of specific key is changed in dictionary
        /// </summary>  
        public event EventHandler<KeyValuePairCancelEventArgs<TKey, TValue>> BeforeSet;
        /// <summary>
        /// Fired after value of specifi key has changed
        /// </summary>
        public event EventHandler<KeyValuePairEventArgs<TKey, TValue>> AfterSet;
        /// <summary>
        /// Fired before dictionary is cleared
        /// </summary>
        public event EventHandler<CancelEventArgs> BeforeClear;
        /// <summary>
        /// Fired afer dictionary has been cleared
        /// </summary>
        public event EventHandler<EventArgs> AfterClear;
        /// <summary>
        /// Fired when key of item changes
        /// </summary>
        public event EventHandler<KeyReplacedEventArgs<TKey>> KeyReplaced;
        #endregion

        #endregion
    }
}
